写给设计师的 Processing 编程指南11-数据储物箱
这节将会介绍一个在 Processing 中非常重要的概念 - 数组。之前储存数据,都会用到 int ,float 这类变量。假设我们有 100 个数据想保存,用老方法就会非常繁琐。下面介绍的数组可以用更便捷的方式存放更多数据。它就有点像一排带有数字编号的箱子,根据编号可以存取需要的信息。
下面先介绍数组的基本语法
数组的声明与创建
数组声明时,必须指定一个储存的数据类型。尽管它能装多个数据,但数据类型必须一致。例如只储存整型数据的整型数组,只储存浮点型数据的浮点型数组。
下面是数组的声明格式:
数据类型 []变量名;
当你希望声明一个整形数组,就可以这么写。数据类型后跟一个方括号。
int[] data;
声明完之后,还不能直接使用。而是需要先对数组进行初始化,初始化的格式为
new 数据类型[数组长度];
因而,声明了以后可以这么写
data = new int[5];
初始化时需要同时指定数组的长度,不能忽略这个步骤,否则就会出错。给数组指定长度,有点像搬家时买纸箱。得先估计物品的多少来决定箱子的个数。除了可以声明了以后再初始化,也能在一行代码中直接完成。
int[] data = new int[5];
当完成这些步骤以后。就可以开始往里面放东西了。通过方括号“[]”和下标就能对数组元素进行读取或者写入。下标相当于是箱子的编号。数组的下标都是从 0 开始的,所以第一个元素的下标为 0。第二个元素下标为 1。以此类推,最后一个元素的下标则为“ 数组长度 - 1 ”。
下标从 0 开始是约定俗成的,虽然有些违反直觉,但只要多用就会熟悉。
熟悉下标后,若要对数组的一个元素进行赋值,可以这样写
data[0] = 10;
如果要读取其中的数据,验证是否有正常保存,可以用 println 来测试。
println(data[0]); // 输出结果为 10
有关数组的基本语法先介绍到这里,下面看一个完整的实例
代码示例(11-1):
int []numbers;
void setup() {
numbers = new int[5];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
println(numbers[0]);
println(numbers[1]);
println(numbers[2]);
println(numbers[3]);
println(numbers[4]);
println(numbers.length);
}
这个例子里创建了一个大小为 5 的整形数组,并对数组元素进行了写入和输出。
末尾的“ numbers.length ” 可以获取数组的大小。格式为” 变量名.length “。
下标不能小于 0 或者大于等于数组的长度。在此例中不能写 numbers[-1] 或 numbers[5],否则程序就会报错
除此以外,赋值还有另外一种方法
int []numbers = {10,20,30,40,50};
这样就无需先指定数组的大小。只要在大括号中,依序写上各个元素,并用逗号隔开即可。
最后,值得注意的是。若是数组声明后,不进行赋值,int 类型的元素值默认都为 0。当类型为 float 时,默认值为 0.0。当类型为 String 时,默认值为 null。它表示空值,代表里面没有任何东西。
用 for 循环对Processing赋值
有些时候我们不希望手动地对每个数组元素进行赋值,这时就要用到 for 循环。下面的例子就结合了随机函数,让每个元素在初始化时都赋上随机值。
代码示例(11-2):
float []numbers;
void setup() {
numbers = new float[5];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = random(100);
}
for (int i = 0; i < numbers.length; i++) {
println(numbers[i]);
}
}
输出结果:
代码说明:
在 for 循环的终结条件中,用到了 numbers.length 。这是一个比较常规的写法。虽然也能直接写成 5。但这么做的好处是,即使前面修改了数组的大小,后面都能保证写入或输出所有的数据。而无需重新修改终结条件的数值。
数组储存人物信息
假如我们希望储存不同类型的数据,如人物信息,货物价格。就可以考虑用不同类型的数组 来储存这些数据。
代码示例(11-3):
String[] name;
boolean[] gender;
float[] heights;
int[] age;
void setup() {
name = new String[]{"Mike", "Jake", "Kate"};
gender = new boolean[]{true, true, false};
heights = new float[]{0.98, 1.34, 1.7};
age = new int[]{5, 10, 18};
for (int i = 0; i < 3; i++) {
println("name:" + name[i]+" gender:" + gender[i] + " height:" + heights[i] + " age:" + age[i]);
}
}
运行结果:
作为“人”这一个体,会有名字,身高,性别这些与之关联的属性。但由于这些属性都是不同的类型。我们不能把它都放到一个数组里。像名字显然属于字符,会用到 String。性别非男即女,可以考虑用boolean。年龄通常是整数,所以用 int。身高一般包含小数,所以用 float。
只要把相应人物的各类属性依序录入。就能通过同一个下标。取出对应人物的属性,获得我们想要的结果。在这个程序中,在同一行内就会分别输出姓名,性别,身高,年龄。
数组绘制随机圆点
下面开始抛开抽象的字符,尝试在图形世界中使用数组。
前面的章节,提到过可以在 randomSeed 在画面上画随机的原点。通过 randomSeed 可以不用创建变量,但这样做显然会有局限。这里学习了数组以后,就能够用简便的方法把每个点的坐标都记录下来。
代码示例(11-4):
float []pointsX, pointsY, radiuses;
int num;
void setup() {
size(700, 700);
num = 50;
pointsX = new float[num];
pointsY = new float[num];
radiuses = new float[num];
for (int i = 0; i < num; i++) {
pointsX[i] = random(width);
pointsY[i] = random(height);
radiuses[i] = random(20, 60);
}
}
void draw() {
background(0);
noStroke();
for (int i = 0; i < num; i++) {
ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
}
}
运行效果:
对照前面的例子,可以用数组实现完全相同的结果
这里创建了两个数组 - pointsX 和 pointsY。因此能分别保存圆点的横纵坐标。
对随机点进行连线
有人可能会有疑惑,既然 randomSeed 也能实现同样效果。那为什么要独立地储存坐标点?下面的例子就通过数组,调取前后两个点的数据,并进行连线。如果是用 randomSeed 的方式,是无法知道上一个 random 产生的数值是什么。因为每次绘图都是用完即弃,并没有用某个容器把数据装起来。
代码示例(11-5):
float []pointsX, pointsY, radiuses;
int num;
void setup() {
size(700, 700);
num = 50;
pointsX = new float[num];
pointsY = new float[num];
radiuses = new float[num];
for (int i = 0; i < num; i++) {
pointsX[i] = random(width);
pointsY[i] = random(height);
radiuses[i] = random(4, 14);
}
}
void draw() {
background(0);
noStroke();
for (int i = 0; i < num; i++) {
ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
if (i > 0) {
stroke(255);
line(pointsX[i], pointsY[i], pointsX[i - 1], pointsY[i - 1]);
}
}
}
运行效果:
代码说明:
绘制直线可以用 line 函数,前后两组坐标分别表示线的两个端点。假如我们想将数组 中的前后两个坐标相连,第一个坐标下标为 i,那前一个坐标自然就是 i - 1。
用 if 语句并把判断条件写成 i > 0 ,是为了保证下标不越界,程序不出错。因为当 i = 0 时,pointX[ i - 1 ] 的下标就为 - 1 。因而需要多加一个判断,跳过当 i = 0 的情况。
小技巧:
在上例的基础上。我们可以创建一个局部变量 showNum 来影响 for 循环的终结条件。最终产生一个连线的动画效果。setup 函数可保持不变,将 draw 函数替换成如下代码
void draw() {
background(0);
noStroke();
int showNum = int(millis()/1000.0 * 5);
if (showNum > num) {
showNum = num;
}
for (int i = 0; i < showNum; i++) {
ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
if (i > 0) {
stroke(255);
line(pointsX[i], pointsY[i], pointsX[i - 1], pointsY[i - 1]);
}
}
}
运行效果:
代码说明:
程序之所以产生动画,与 showNum 的数值有关
在创建 showNum 时,它就是一个随着程序运行时间越长,数值不断增大的变量。在这里每过一秒,数值就会增大 5。
最开始 for 循环的终结条件写的是” i < num “,这会将数组 中的点一并画出来。但如果取的是一个变化的数值。就可以从下标为 0 的元素开始,一点一点地展示数据
之后若想制作一个画板,播放绘画轨迹。就能用类似的方式去实现。
用beginShape,endShape 画折线
前面的例子结合了 if 条件语句。逐条绘制了直线。下面将介绍一个方法,会让写法更简洁。先看基本实例
代码示例(11-6):
void setup() {
size(700, 700);
}
void draw() {
background(0);
beginShape();
vertex(350,100);
vertex(100,600);
vertex(600,600);
endShape();
}
运行结果:
代码说明:
在绘制图形时,会用到beginShape 和 endShape 进行包裹。beginShape 放开头,endShape 放末尾
其中vertex 函数放在两者之间,它表示顶点,其中的两个参数作为点的横纵坐标。
程序中写了三个 vertex 函数,因而会把三个顶点依序连起来
由于没有写 noFill 函数,所以绘制的结果会是填充图形,而不是折线。只要在其中加上
-
noFill();
stroke(255);
就能只显示轮廓,也就成了折线。
掌握了这种写法,例 11-5 的 draw 函数部分就能写成。
void draw() {
background(0);
noStroke();
beginShape();
for (int i = 0; i < num; i++) {
ellipse(pointsX[i], pointsY[i], radiuses[i], radiuses[i]);
vertex(pointsX[i], pointsY[i]);
}
endShape();
}
运行结果是一样的:
数组绘制彩色方块
下面将介绍 color型数组 的使用方法,先看示例
代码示例(11-7):
float[] rectsW, rectsH;
color[] colors;
int num;
void setup() {
size(700, 700);
num = 150;
rectsW = new float[num];
rectsH = new float[num];
colors = new color[num];
for (int i = 0; i < num; i++) {
rectsW[i] = random(200);
rectsH[i] = random(600);
colors[i] = color(random(255), random(255), random(255));
}
}
void draw() {
background(0);
rectMode(CENTER);
blendMode(ADD);
for (int i = 0; i < num; i++) {
float alpha = mouseX/(float)width * 255;
fill(colors[i], alpha);
float x = width/(float)num * i;
rect(x, height/2, rectsW[i], rectsH[i]);
}
}
运行效果:
代码说明:
color 在声明时和其他类型并没有差别。但赋值时要注意单位,不能省略掉 color()。它必须是作为 color 型的数据,才能被 存储 到数组中。
写程序时要考虑哪些数据需要用数组储存。若有固定规律可以不用。在这个例子中,矩形的纵坐标都是固定的,而横坐标可以用 i 推算。因此就不需要用额外的数组来储存。
使用加色模式,可以让图形叠加后,更容易产生辉光效果。这里创建了局部变量 alpha,通过移动鼠标就能改变色彩的透明度
若去掉加色模式,就能看到矩形的原始色彩效果
数组在圆周运动的应用(1)
前面有介绍过如何用三角函数来制作圆周运动。单个物体作圆周运动,就需要创建三个变量来分别保存角度,横坐标与纵坐标。若想创建多个物体,就可以考虑用数组。这样每个属性都能独立控制。
代码示例(11-8):
float[] circlesX, circlesY, speed, angle;
int num;
void setup() {
size(700, 700);
num = 130;
circlesX = new float[num];
circlesY = new float[num];
speed = new float[num];
angle = new float[num];
for (int i = 0; i < num; i++) {
speed[i] = random(0.002, 0.03);
}
}
void draw() {
background(0);
translate(width/2,height/2);
for (int i = 0; i < num; i++) {
float r = 30 + i * 2;
angle[i] += speed[i];
circlesX[i] = r * cos(angle[i]);
circlesY[i] = r * sin(angle[i]);
ellipse(circlesX[i], circlesY[i], 8,8);
if (i > 0) {
stroke(255);
line(circlesX[i], circlesY[i], circlesX[i - 1], circlesY[i - 1]);
}
}
}
运行效果:
代码说明:
因为希望每个圆的运动速度都有区别,所以创建了 speed 变量来独立储存速度信息
还可以用之前提到的beginShape,endShape 来画折线。结果都是一样的。但对于用beginShape,endShape 画的图形,若开启填充效果,就会有非常特别的效果。试着把上例的 draw 函数按如下方式替换。
void draw() {
background(0);
translate(width/2, height/2);
noStroke();
beginShape();
for (int i = 0; i < num; i++) {
float r = 30 + i * 2;
angle[i] += speed[i];
circlesX[i] = r * cos(angle[i]);
circlesY[i] = r * sin(angle[i]);
ellipse(circlesX[i], circlesY[i], 8, 8);
vertex(circlesX[i], circlesY[i]);
}
endShape();
}
运行效果:
这样就能将绘制的折线变成多边形。由于程序中会自动把重叠的部分进行反色处理,因而产生了上面的效果。
数组在圆周运动的应用(2)
基于上面的代码结构,并对参数进行一些修改。试着 将 draw 函数中的 background 写在 setup 函数中试试。
代码示例(11-9):
float[] circlesX, circlesY, speed, angle;
int num;
void setup() {
size(700, 700);
num = 5;
circlesX = new float[num];
circlesY = new float[num];
speed = new float[num];
angle = new float[num];
for (int i = 0; i < num; i++) {
speed[i] = random(0.002, 0.03);
}
background(0);
}
void draw() {
translate(width/2, height/2);
noFill();
stroke(255,50);
beginShape();
for (int i = 0; i < num; i++) {
float r = 30 + i * 60;
angle[i] += speed[i];
circlesX[i] = r * cos(angle[i]);
circlesY[i] = r * sin(angle[i]);
ellipse(circlesX[i], circlesY[i], 8, 8);
vertex(circlesX[i], circlesY[i]);
}
endShape();
}
点的运动轨迹:
实际运行效果:
仅有黑白二色难免单调。结合前面提到的色彩模式,可以让图形产生富有层次变化的效果。试着替换着色部分的代码
blendMode(ADD);
colorMode(HSB);
stroke(int(millis()/50.0) % 255,255,255,10);
运行效果:
关于数组还有更多高级用法,后面的时间就交给大家尽情探索~
Processing 系列文章
[1][2][3][4][5][6][7][8][9][10]
资源索引